#include <i386.h>
#include <tipos.h>
#include "vga.h"
#include "string.h"

uint_16 vga_port = 0x3D0;

uint_8* vga_addr = (uint_8*)0xB8000;
const uint_16 vga_cols = VGA_SCREEN_COLS;
const uint_16 vga_rows = VGA_SCREEN_ROWS;

uint_16 vga_cursor_col = 0;
uint_16 vga_cursor_row = 0;

#define SET_CURSOR(f, c) \
	if ( f != USE_CURSOR ) \
		vga_cursor_row = f; \
	if ( c != USE_CURSOR ) \
		vga_cursor_col = c;
		
#define ADJ_CURSOR \
		if (vga_cursor_row >= vga_rows) { \
			/* vga_cursor_row = 0; */ \
			vga_cursor_col = 0; \
			vga_scroll(1); \
		} \
		if (vga_cursor_col >= vga_cols) { \
			vga_cursor_row++; \
			vga_cursor_col = vga_cursor_col % vga_cols; \
		}
		
#define PREV_CURSOR \
		if (vga_cursor_col > 0) { \
			vga_cursor_col--; \
		} \
		else { \
			if (vga_cursor_row > 0) { \
				vga_cursor_row--; \
				vga_cursor_col = vga_cols - 1; \
			} \
		} \

#define PUT_CURSOR(c, a) \
		*(vga_addr + (vga_cursor_row * vga_cols + vga_cursor_col) * 2) = c; \
		*(vga_addr + (vga_cursor_row * vga_cols + vga_cursor_col) * 2 + 1) = a; \
		vga_cursor_col++;
		
#define LF_CURSOR \
		vga_cursor_col = 0; \
		vga_cursor_row++;
		
#define TAB_CURSOR \
		vga_cursor_col+=4;


void vga_load_screen(const char *screen) {
	memcpy(vga_addr, screen, VGA_SCREEN_COLS * VGA_SCREEN_ROWS * 2);
}

void vga_init(void) {
}

void vga_write(uint_16 f, uint_16 c, const char* msg, uint_8 attr) {

	int i;
	
	SET_CURSOR(f, c)

	for(i = 0; msg[i] != '\0'; i++) {
		ADJ_CURSOR
		switch (msg[i]) {
				case '\n':
					LF_CURSOR
					break;
				case '\t':
					TAB_CURSOR
					break;
				default:
					PUT_CURSOR(msg[i], attr)
			}
	}
}

void vga_print(uint_16 f, uint_16 c, const char* msg, uint_8 attr, uint_32 size) {

	int i;
	
	SET_CURSOR(f, c)

	for(i = 0; i < size; i++) {

		switch (msg[i]) {
				case '\n':
					ADJ_CURSOR
					LF_CURSOR
					break;
				case '\t':
					ADJ_CURSOR
					TAB_CURSOR
					break;
				case '\b':
					PREV_CURSOR
					PUT_CURSOR(' ', attr)
					vga_cursor_col--;
					break;
				default:
					ADJ_CURSOR
					PUT_CURSOR(msg[i], attr)
			}
	}
}

void vga_printf(uint_16 f, uint_16 c, char* format, uint_8 attr, ...) {

	uint_32 *param;
	char *pchar;
	char buffer[16];
	
	SET_CURSOR(f, c)

	param = (uint_32 *) rebp();
	param += 6;

	for(pchar = format; *pchar != '\0'; pchar++) {

		ADJ_CURSOR
		
		if (*pchar == '%') {
			pchar++;
			switch (*pchar) {
				case 'd':  
					itoa(*param, buffer, 10);
					vga_write(USE_CURSOR, USE_CURSOR, buffer, attr);
					break;
				case 'x':
					itoa(*param, buffer, 16);
					vga_write(USE_CURSOR, USE_CURSOR, buffer, attr);
					break;
				case 's':
					vga_write(USE_CURSOR, USE_CURSOR, (char *) *param, attr);
					break;
				case '%':
					PUT_CURSOR('%', attr)
					param--;
					break;
			}
			param++;
			continue;
		}
		
		switch (*pchar) {
				case '\n':
					LF_CURSOR
					break;
				case '\t':
					TAB_CURSOR
					break;
				default:
					PUT_CURSOR(*pchar, attr)
			}
	}
}

/* Nice to Have */
void
vga_clear()
{
	memset(vga_addr, '\0', (( vga_cols * vga_rows ) << 1));	
	vga_cursor_col = 0;
	vga_cursor_row = 0;
}

void
vga_scroll(uint_8 rows)
{
	uint_32 dif = (rows * vga_cols) << 1;
	uint_32 all = (vga_rows * vga_cols) << 1;
	
	memcpy( vga_addr, vga_addr + dif, all - dif);
	memset( vga_addr + all - dif, '\0', dif);
	
	if ( vga_cursor_row >= rows )
		vga_cursor_row -= rows;
	else
	{
		vga_cursor_row = 0;
		vga_cursor_col = 0;
	}
}
